package cn.iocoder.yudao.framework.util;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Objects;

/**
 * <pre>
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * 基于事件计数器的一次性密码（HOTP - HMAC-Based One-Time Password）。
 * 基于时间的一次性密码（TOTP - Time-based One-Time Password）。
 *
 * 关于门禁卡动态密码的实现，可以从以下几个方面来理解：
 * 单频动态密码/滚动式密码：每次开门时生成一个唯一的只能使用一次的密码。
 * 技术实现要点：
 * 密钥同步：门禁卡和门禁系统需要共享相同的密钥，可采用事件计数器或时间保持同步。
 * 算法选择：常用的算法有HOTP、TOTP等。
 * 安全性考虑：确保传输过程中的加密，防止中间人攻击。
 * 应用场景：适用于对安全性要求较高的场所，如企业办公楼、数据中心、政府机关等。
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * </pre>
 *
 * @author 山野羡民（1032694760@qq.com）
 * @since 2025/02/11
 * @see cn.hutool.crypto.digest.otp.HOTP
 * @see cn.hutool.crypto.digest.otp.TOTP
 */
@SuppressWarnings("unused")
public class HotpUtils {
    private static final String ALGORITHM = "HmacSHA1";
    private static final int OTP_LENGTH = 6;

    public static String generateSecretKey(int length) {
        return Holder.INSTANCE.generateSecretKey(length);
    }

    public static String generateOTP(String secretKey, int length) {
        return Holder.INSTANCE.generateOTP(secretKey, length);
    }

    public static boolean verifyOTP(String secretKey, int length, String userOTP) {
        return Holder.INSTANCE.verifyOTP(secretKey, length, userOTP);
    }

    public static HOTP getInstance() {
        return Holder.INSTANCE;
    }

    // 使用示例
    public static void main(String[] args) {
        // 指定密钥
        String secretKey = "K7LE67JNH7RHYPJW3ALFTE";
        System.out.println("Secret Key: " + secretKey);
        // 生成动态密码
        String otp = HotpUtils.generateOTP(secretKey, OTP_LENGTH);
        System.out.println("Generated OTP: " + otp);
        // 验证动态密码
        String userOTP = "036419"; // 用户输入的动态密码
        if (HotpUtils.verifyOTP(secretKey, OTP_LENGTH, userOTP)) {
            System.out.println("动态密码验证成功！");
        } else {
            System.out.println("动态密码验证失败！");
        }
    }

    public static class Holder {
        private static final HOTP INSTANCE = new HOTP();
    }

    @SuppressWarnings("unused")
    public static class HOTP {
        // Base32 编码表，根据实际业务调整，保证长度为 32 即可
        private final String base32Chars;
        // 时间步长，根据实际业务调整，默认是 30 秒
        private final int timeStep;

        public HOTP() {
            this("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", 30);
        }

        public HOTP(String base32Chars, int timeStep) {
            if (base32Chars.length() != 32) {
                throw new IllegalArgumentException("Base32 string length must be 32");
            }
            this.base32Chars = base32Chars;
            if (timeStep <= 0) {
                throw new IllegalArgumentException("Time slice step must be more than 0");
            }
            this.timeStep = timeStep;
        }

        /**
         * 生成一个随机密钥，使用 Base32 编码算法，默认是长度为 12 的字符串
         */
        public String generateSecretKey() {
            return generateSecretKey(12);
        }

        /**
         * 生成一个随机密钥，使用 Base32 编码算法
         */
        public String generateSecretKey(int length) {
            byte[] randomBytes = new byte[length];
            SecureRandom random = new SecureRandom();
            random.nextBytes(randomBytes);
            return base32Encode(randomBytes);
        }

        /**
         * 根据当前时间生成动态密码，使用 HMAC-SHA1 算法，默认是长度为 6 的数字
         */
        public String generateOTP(String secretKey) {
            return generateOTP(secretKey, OTP_LENGTH);
        }

        /**
         * 根据当前时间生成动态密码，使用 HMAC-SHA1 算法
         */
        public String generateOTP(String secretKey, int length) {
            // Calculate the counter by time-based
            long counter = (System.currentTimeMillis() / 1000L) / timeStep;
            return generateOTP(secretKey, counter, length);
        }

        /**
         * 根据事件计数器生成动态密码，使用 HMAC-SHA1 算法
         */
        public String generateOTP(String secretKey, long counter, int length) {
            byte[] decodeKey = base32Decode(secretKey);
            byte[] hash = hmacSha1(decodeKey, longToBytes(counter));
            // Take the 4 least significant bits of the hash
            int offset = hash[hash.length - 1] & 0x0F;
            // Take the 4 bytes after the offset
            int binary = ((hash[offset] & 0x7F) << 24) |
                         ((hash[offset + 1] & 0xFF) << 16) |
                         ((hash[offset + 2] & 0xFF) << 8) |
                         (hash[offset + 3] & 0xFF);
            // Mask off the top bit so we have a 31-bit number
            binary &= 0x7FFFFFFF;
            // Take the last digits based on the specified length
            int otp = binary % (int) Math.pow(10, length);
            return String.format("%0" + length + "d", otp);
        }

        /**
         * 验证用户输入的动态密码是否正确
         */
        public boolean verifyOTP(String secretKey, String userOTP) {
            return verifyOTP(secretKey, OTP_LENGTH, userOTP);
        }

        /**
         * 验证用户输入的动态密码是否正确
         */
        public boolean verifyOTP(String secretKey, int length, String userOTP) {
            String expectedOTP = generateOTP(secretKey, length);
            return Objects.equals(expectedOTP, userOTP);
        }

        /**
         * 验证用户输入的动态密码是否正确
         */
        public boolean verifyOTP(String secretKey, long counter, int length, String userOTP) {
            String expectedOTP = generateOTP(secretKey, counter, length);
            return Objects.equals(expectedOTP, userOTP);
        }

        /**
         * Base32 编码算法
         */
        private String base32Encode(byte[] data) {
            StringBuilder output = new StringBuilder();
            int bits = 0;
            int value = 0;
            for (byte b : data) {
                value = (value << 8) | (b & 0xFF);
                bits += 8;
                while (bits >= 5) {
                    output.append(base32Chars.charAt((value >> (bits - 5)) & 31));
                    bits -= 5;
                }
            }
            if (bits > 0) {
                output.append(base32Chars.charAt((value << (5 - bits)) & 31));
            }
            return output.toString();
        }

        /**
         * Base32 解码算法
         */
        private byte[] base32Decode(String data) {
            data = data.replace("=", ""); // Remove padding
            byte[] output = new byte[(data.length() * 5 + 7) / 8];
            int bits = 0;
            int value = 0;
            int index = 0;
            for (char c : data.toCharArray()) {
                int charIndex = base32Chars.indexOf(c);
                if (charIndex == -1) {
                    throw new IllegalArgumentException("Invalid character in Base32 string");
                }
                value = (value << 5) | charIndex;
                bits += 5;
                while (bits >= 8) {
                    output[index++] = (byte) ((value >> (bits - 8)) & 0xFF);
                    bits -= 8;
                }
            }
            return output;
        }

        /**
         * HMAC-SHA1 算法
         */
        private byte[] hmacSha1(byte[] keyBytes, byte[] text) {
            try {
                Mac mac = Mac.getInstance(ALGORITHM);
                mac.init(new SecretKeySpec(keyBytes, ALGORITHM));
                return mac.doFinal(text);
            } catch (NoSuchAlgorithmException | InvalidKeyException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * 将 long 类型转换为 byte 数组
         */
        private byte[] longToBytes(long value) {
            byte[] result = new byte[8];
            for (int i = 7; i >= 0; i--) {
                result[i] = (byte) (value & 0xFF);
                value >>= 8;
            }
            return result;
        }

    }

}
